/**
 * @file    memory.c
 * @author  Thomas Di'Meco and Matthieu Maugard
 * @brief   Memory et registers management
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "memory.h"
#include "instructions.h"


word_t mem[MEMORY_SIZE] = {EMPTY_WORD};

int16_t reg[REGISTERS_NUMBER] = {0};
int16_t pc = 0;
int16_t sp = MEMORY_SIZE-1;
int16_t sr = 0;


/**
 * Extract the source mode from the instruction mode
 * @param mode The instruction mode
 * @return The source mode
 */
int16_t get_src_mode (int16_t mode)
{
    if (mode == REGREG || mode == DIRREG || mode == INDREG)
        return REG;
    else if (mode == REGIMM || mode == DIRIMM || mode == INDIMM)
        return IMM;
    else if (mode == REGDIR)
        return DIR;
    else if (mode == REGIND)
        return IND;

    return UNK;
}


/**
 * Extract the destination mode from the instruction mode
 * @param mode The instruction mode
 * @return The destination mode
 */
int16_t get_dest_mode (int16_t mode)
{
    if (mode == REGREG || mode == REGIMM || mode == REGDIR || mode == REGIND)
        return REG;
    else if (mode == DIRIMM || mode == DIRREG)
        return DIR;
    else if (mode == INDIMM || mode == INDREG)
        return IND;

    return UNK;
}


/**
 * Combine the destination mode and the source mode into
 * the corresponding instruction mode
 * @param dest_mode The destination mode
 * @param src_mode  The source mode
 * @return The instruction mode
 */
int16_t get_mode (int16_t dest_mode, int16_t src_mode)
{
    if      (dest_mode == REG && src_mode == REG) return REGREG;
    else if (dest_mode == REG && src_mode == IMM) return REGIMM;
    else if (dest_mode == REG && src_mode == DIR) return REGDIR;
    else if (dest_mode == REG && src_mode == IND) return REGIND;
    else if (dest_mode == DIR && src_mode == IMM) return DIRIMM;
    else if (dest_mode == DIR && src_mode == REG) return DIRREG;
    else if (dest_mode == IND && src_mode == IMM) return INDIMM;
    else if (dest_mode == IND && src_mode == REG) return INDREG;

    return UNKUNK;
}


/**
 * Returns true if mem is a valid memory address
 * @param mem The memory address
 * @return 1 if the memory address is valid, 0 otherwise
 */
int is_mem (int16_t mem)
{
    return (0 <= mem && mem < MEMORY_SIZE);
}


/**
 * Returns true if reg is a valid register
 * @param reg The register number
 * @return 1 if the register is valid, 0 otherwise
 */
int is_reg (int16_t reg)
{
    return (0 <= reg && reg < REGISTERS_NUMBER);
}


/**
 * Dump the current memory state to the standard output
 */
void dump_mem ()
{
    printf("+---MEMDUMP---+\n");

    int w;
    for (w = 0; w < MEMORY_SIZE; w++)
        printf("| %04x: %02X %02X |\n", w, mem[w].raw >> 8 & 0x00FF, mem[w].raw & 0x00FF);

    printf("+-------------+\n");
}


/**
 * Dump registers to the standard output
 */
void dump_reg ()
{
    printf("+--REGDUMP--+\n");

    int r;
    for (r = 0; r < REGISTERS_NUMBER; r++)
        printf("| R%d: %02X %02X |\n", r, reg[r] >> 8 & 0x00FF, reg[r] & 0x00FF);

    printf("| PC: %02X %02X |\n", pc >> 8 & 0x00FF, pc & 0x00FF);
    printf("| SP: %02X %02X |\n", sp >> 8 & 0x00FF, sp & 0x00FF);
    printf("| SR: %02X %02X |\n", sr >> 8 & 0x00FF, sr & 0x00FF);

    printf("+-----------+\n");
}


/**
 * Convert a binary word into PROCSI mnemonic textual instruction
 * @param word The memory address of the word
 * @param str  The string buffer you want to store the result
 * @param size The size of the string buffer
 * @return     An error code != 0 if an error occured,
 *             0 otherwise
 */
int mnemonic_instruction (int16_t word, char * str, int size)
{
    // Get words
    word_t words[3] = {EMPTY_WORD};
    if (is_mem(word))   words[0] = mem[word];
    if (is_mem(word+1)) words[1] = mem[word+1];
    if (is_mem(word+2)) words[2] = mem[word+2];

    char inst[100] = "";

    // Codeop
    if (words[0].inst.codeop >= INSTRUCTION_NUMBER) return 1;
    strcat(inst, instruction_str[words[0].inst.codeop]);

    // Modes
    int16_t src_mode  = get_src_mode(words[0].inst.mode);
    int16_t dest_mode = get_dest_mode(words[0].inst.mode);

    // Get mode and value
    int zero_operand = 0; // Boolean
    int nword = 1;
    int op;
    for (op = 1; op < 3; op++) // For each operand (op = 1, then op = 2)
    {
        int16_t mode;
        int16_t value;

        // Get operand mode
        switch (words[0].inst.codeop)
        {
            // 0-operand instructions
            case HALT:
            case RET:
            {
                zero_operand = 1;
                break;
            }
            // 1-operand destination instructions
            case POP:
            {
                mode = dest_mode;
                if (mode == REG || mode == IND)
                    value = words[0].inst.dest;
                else
                    value = words[nword].raw;

                op = 3; // To exit 'for' next operand
                break;
            }
            // 1-operand source instructions
            case JMP:
            case JEQ:
            case CALL:
            case PUSH:
            {
                mode = src_mode;
                if (mode == REG || mode == IND)
                    value = words[0].inst.src;
                else
                    value = words[nword].raw;

                op = 3; // To exit 'for' next operand
                break;
            }
            // Classic 2-operands instructions
            default:
            {
                if (op == 1)
                {
                    mode = dest_mode;
                    if (mode == REG || mode == IND)
                        value = words[0].inst.dest;
                    else
                        value = words[nword++].raw;
                }
                else
                {
                    mode = src_mode;
                    if (mode == REG || mode == IND)
                        value = words[0].inst.src;
                    else
                        value = words[nword].raw;
                }

                break;
            }
        }

        // If 0-operand instruction: break
        if (zero_operand) break;

        // Add comma
        if (op == 2) strcat(inst, ",");

        // Add space
        strcat(inst, " ");

        // Add [
        if (mode == IND || mode == DIR) strcat(inst, "[");

        // Add R
        if (mode == REG || mode == IND) strcat(inst, "R");
        // Add #
        else strcat(inst, "#");

        // Value
        char v[16];
        sprintf(v, "%d", value);
        strcat(inst, v);

        // Add ]
        if (mode == IND || mode == DIR) strcat(inst, "]");
    }

    // Copy resulting instruction string into str
    if (strlen(inst) <= size) strcpy(str, inst);

    // Returns no error
    return 0;
}
